#include <iostream>
#include <cmath>
#include <cstdio>
#include <string>
#include <cstdlib>
#include <fstream>
#include <cstring>
#include <algorithm>
#include <ctime>
using namespace std;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef long LONG;
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)

#pragma pack(1)
//调色板与颜色表
typedef struct RGBQUAD
{
    BYTE rgbBlue;
    BYTE rgbGreen;
    BYTE rgbRed;
    BYTE rgbReserved;
} BGRA;
//位图文件头，为14个字节
typedef struct tagBITMAPFILEHEADER
{
    WORD bfType;
    DWORD bfSize;     //文件大小
    WORD bfReserved1; //保留字，不考虑
    WORD bfReserved2; //保留字，同上
    DWORD bfOffBits;  //实际位图数据的偏移字节数，即前三个部分长度之和
} BmpFileHeader;
//位图信息头，为40个字节
typedef struct tagBITMAPINFOHEADER
{
    DWORD biSize;         //指定此结构体的长度，为40
    LONG biWidth;         //位图宽
    LONG biHeight;        //位图高
    WORD biPlanes;        //平面数，为1
    WORD biBitCount;      //采用颜色位数，可以是1，2，4，8，16，24，新的可以是32
    DWORD biCompression;  //压缩方式，可以是0，1，2，其中0表示不压缩
    DWORD biSizeImage;    //实际位图数据占用的字节数
    LONG biXPelsPerMeter; //X方向分辨率
    LONG biYPelsPerMeter; //Y方向分辨率
    DWORD biClrUsed;      //使用的颜色数，如果为0，则表示默认值(2^颜色位数)
    DWORD biClrImportant; //重要颜色数，如果为0，则表示所有颜色都是重要的
} BmpInfoHeader;

//bmp图像类
class BmpImage
{
private:
    BmpFileHeader Fheader; //图像文件头
    BmpInfoHeader Iheader; //图像信息头
    RGBQUAD **palette;     //调色板
    BGRA **bgra;           //24/32位用的rgba颜色
    long width;            //宽度
    long height;           //高度
    WORD depth;            //深度
    BYTE *idata;           //存储图像的数据
    int l_width;           //补齐宽度
    int grayhist[256];     //灰度直方图数组
public:
    BYTE rgbarray[4]; //暂时存储某一个像素的rgba颜色表的四个成员，方便运算,并且需要将其公有化方便调用
                      //将某个像素的rgba颜色表放在一个数组中，方便运算
    void addArray(long i, long j)
    {
        memset(rgbarray, 0, 4);
        rgbarray[0] = bgra[i][j].rgbRed;
        rgbarray[1] = bgra[i][j].rgbGreen;
        rgbarray[2] = bgra[i][j].rgbBlue;
        rgbarray[3] = bgra[i][j].rgbReserved;
    }
    //处理某个像素在图像运算之后的溢出问题
    void clearOverflow()
    {
        int i = 0;
        for (i = 0; i < 4; ++i)
        {
            if (rgbarray[i] > 255)
                rgbarray[i] = 255;
            if (rgbarray[i] < 0)
                rgbarray[i] = 0;
        }
    }
    //将某个像素的颜色数组释放到颜色表中，用来更改成运算后的结果
    void popArray(long i, long j)
    {
        bgra[i][j].rgbRed = rgbarray[0];
        bgra[i][j].rgbGreen = rgbarray[1];
        bgra[i][j].rgbBlue = rgbarray[2];
        bgra[i][j].rgbReserved = rgbarray[3];
        memset(rgbarray, 0, 4);
    }
    explicit BmpImage(char *filename) //构造函数
    {
        if (!readBmp(filename))
        {
            printf("Failed to create the bmpimage %s\n", filename);
        }
        else
        {
            printf("Succeed to create the bmpimage %s\n", filename);
        }
    }
    bool readBmp(char *filename)
    {
        ifstream infile(filename, ios::binary);
        // 检查文件是否打开
        if (!infile.is_open())
        {
            printf("Failed to open file %s\n", filename);
            return false;
        }
        // 读取BmpFileHeader
        infile.read((char *)(&Fheader), sizeof(BmpFileHeader));
        // 判断是否是bmp图像
        if (Fheader.bfType != 0x4d42) // 0x4d42 = 'BM'
        {
            printf("File %s is not bmp file\n", filename);
            return false;
        }
        // 读取BmpInfoHeader
        infile.read((char *)(&Iheader), sizeof(BmpInfoHeader));
        // 判断是否有调色板，如果有就读取调色板数据
        if (Fheader.bfOffBits == 54)
        { //说明文件头和信息头的大小就是偏移量，没有调色板
            palette = nullptr;
        }
        else
        {
            // 读取调色板数据,24/32位颜色表赋空值
            infile.read((char *)(palette), Fheader.bfOffBits - 54);
            bgra = nullptr;
        }
        // 给属性赋值
        width = Iheader.biWidth;
        height = Iheader.biHeight;
        depth = Iheader.biBitCount;
        memset(rgbarray, 0, 4);
        // 读取图像数据
        // 申请内存空间并初始化为0
        l_width = WIDTHBYTES(width * depth);
        long datasize = height * l_width;
        idata = new BYTE[datasize];
        memset(idata, 0, datasize);
        // 读取图像数据到内存
        infile.read((char *)idata, datasize);
        //图像是24或32位时，运行rgba()函数，将像素的rgba值保存到数组bgra中
        if (depth == 32 || depth == 24)
        {
            rgba();
        }
        infile.close();
        return true;
    }
    //如果图像是32/24位，求图像RGBA值
    bool rgba()
    {
        if (depth != 24 && depth != 32)
        {
            printf("The image is not 24bit or 32bit\n");
            return false;
        }
        //补齐宽度
        l_width = WIDTHBYTES(width * depth);
        bgra = NULL;
        long i, j;
        bgra = new BGRA *[height];
        for (i = 0; i < height; i++)
            bgra[i] = new BGRA[width];
        int k;
        int index = 0;
        if (depth == 32)
            for (i = 0; i < height; i++)
            {
                int k0 = (height - i - 1) * l_width;
                for (j = 0; j < width; j++)
                {
                    k = k0 + (j * 4);
                    bgra[i][j].rgbRed = idata[k + 2];
                    bgra[i][j].rgbGreen = idata[k + 1];
                    bgra[i][j].rgbBlue = idata[k];
                    bgra[i][j].rgbReserved = idata[k + 3];
                }
            }
        if (depth == 24)
            for (i = 0; i < height; i++)
            {
                int k0 = (height - i - 1) * l_width;
                for (j = 0; j < width; j++)
                {
                    k = k0 + (j * 3);
                    bgra[i][j].rgbRed = idata[k + 2];
                    bgra[i][j].rgbGreen = idata[k + 1];
                    bgra[i][j].rgbBlue = idata[k];
                    bgra[i][j].rgbReserved = 0;
                }
            }
        return true;
    }
    //显示bmp文件头属性
    void showBmpFileHead()
    {
        printf("BmpFileHead：\n");
        printf("Filetype:%x\n", Fheader.bfType);
        printf("Filesize:%d\n", Fheader.bfSize);
        printf("Reserved1:%d\n", Fheader.bfReserved1);
        printf("Reserved2:%d\n", Fheader.bfReserved2);
        printf("Offbits:%d\n", Fheader.bfOffBits);
    }
    //显示bmp信息头属性
    void showBmpInfoHead()
    {
        printf("BmpInfoHead：\n");
        printf("Width:%d\n", Iheader.biWidth);
        printf("Height:%d\n", Iheader.biHeight);
        printf("Planes:%d\n", Iheader.biPlanes);
        printf("Bitcount:%d\n", Iheader.biBitCount);
        printf("Compression:%d\n", Iheader.biCompression);
        printf("Sizeimage:%d\n", Iheader.biSizeImage);
        printf("X-pixels per meter:%d\n", Iheader.biXPelsPerMeter);
        printf("Y-pixels per meter:%d\n", Iheader.biYPelsPerMeter);
        printf("Colors used:%d\n", Iheader.biClrUsed);
        printf("Important colors used:%d\n", Iheader.biClrImportant);
    }
    //将图像32位保存到指定文件
    bool saveBmp(char *filename)
    {
        long i, j;
        ofstream outfile(filename, ios::binary);
        Iheader.biBitCount = 32;
        outfile.write((char *)&Fheader, sizeof(Fheader));
        outfile.write((char *)&Iheader, sizeof(Iheader));
        int lwidth = WIDTHBYTES(width * 32) - width * 4; //计算为确保位图数据区的实际宽度为32字节的整数倍需添加的0字节个数
        for (i = 0; i < height; i++)
        {
            for (j = 0; j < width; j++)
            {
                outfile.write((char *)(&bgra[height - i - 1][j]), 4);
            }
            BYTE tmp = 0;
            for (j = 0; j < lwidth; j++)
                outfile << tmp;
        }
        cout << "Succeed to save bmpfile on " << filename << endl;
        outfile.close();
        return true;
    }
    //rgb图像转灰度图像
    void convertRGB2Gray()
    {
        double gray;
        for (long i = 0; i < height; i++)
        {
            for (long j = 0; j < width; j++)
            {
                gray = 0.299 * bgra[i][j].rgbRed + 0.587 * bgra[i][j].rgbGreen + 0.114 * bgra[i][j].rgbBlue;
                bgra[i][j].rgbRed = (BYTE)gray;
                bgra[i][j].rgbGreen = (BYTE)gray;
                bgra[i][j].rgbBlue = (BYTE)gray;
            }
        }
        printf("Succeed to convert rgb to gray\n");
    }
    //生成灰度直方图
    void saveHist()
    {
        // 生成灰度直方图数组
        memset(grayhist, 0, 256);
        convertRGB2Gray();
        for (int i = 0; i < height; i++)
        {
            for (int j = 0; j < width; j++)
            {
                grayhist[bgra[i][j].rgbBlue]++;
            }
        }
    }
    //-------------------------------第5.1节 均值滤波、中值滤波和加权均值滤波---------------
    //用随机数添加椒盐噪声,并返回文件名
    void addSalt(int n = 10)
    {
        srand(int(time(0)));
        long i, j;
        int k;
        int x;
        for (k = 0; k < n; ++k)
        {
            i = rand() % height;
            j = rand() % width;
            addArray(i, j);
            for (x = 0; x < 4; ++x)
            {
                rgbarray[x] = 0;
            }
            popArray(i, j);
        }
        char str[] = "salted.bmp";
        printf("Succeed to add salt on %s\n", str);
        saveBmp(str);
        rgba();
    }
    //3x3均值滤波器
    void meanFilter()
    { //定义两个灰度数组，一个记录原始灰度值，另一个计算得出目的文件的灰度
        BYTE sgrayArray[height][width];
        BYTE dgrayArray[height][width];
        memset(sgrayArray, 0, sizeof(sgrayArray));
        memset(dgrayArray, 0, sizeof(dgrayArray));
        //图像转灰度
        convertRGB2Gray();
        long i, j;
        for (i = 0; i < height; ++i)
        {
            for (j = 0; j < width; ++j)
            {
                sgrayArray[i][j] = bgra[i][j].rgbBlue;
            }
        }
        memcpy_s(dgrayArray, sizeof(dgrayArray), sgrayArray, sizeof(sgrayArray));
        long k, l;
        int sum = 0;
        for (i = 1; i < height - 1; ++i)
        {
            for (j = 1; j < width - 1; ++j)
            {
                sum = 0;
                for (k = i - 1; k <= i + 1; ++k)
                {
                    for (l = j - 1; l <= j + 1; ++l)
                    {
                        sum += sgrayArray[k][l];
                    }
                }
                dgrayArray[i][j] = sum / 9;
            }
        }
        for (i = 0; i < height; ++i)
        {
            for (j = 0; j < width; ++j)
            {
                addArray(i, j);
                for (k = 0; k < 4; ++k)
                {
                    rgbarray[k] = dgrayArray[i][j];
                }
                popArray(i, j);
            }
        }
        char str[] = "meanfilter.bmp";
        printf("Succeed to mean-filter on %s\n", str);
        saveBmp(str);
    }
    //中值滤波器,3x3矩阵中的中间值
    void medianFilter()
    {
        //定义两个灰度数组，一个记录原始灰度值，另一个计算得出目的文件的灰度
        BYTE sgrayArray[height][width];
        BYTE dgrayArray[height][width];
        memset(sgrayArray, 0, sizeof(sgrayArray));
        memset(dgrayArray, 0, sizeof(dgrayArray));
        //图像转灰度
        convertRGB2Gray();
        //只储存3x3矩阵数值的数组
        BYTE arr[9];
        long i, j;
        for (i = 0; i < height; ++i)
        {
            for (j = 0; j < width; ++j)
            {
                sgrayArray[i][j] = bgra[i][j].rgbBlue;
            }
        }
        memcpy_s(dgrayArray, sizeof(dgrayArray), sgrayArray, sizeof(sgrayArray));
        long k, l;
        int x;
        for (i = 1; i < height - 1; ++i)
        {
            for (j = 1; j < width - 1; ++j)
            {
                memset(arr, 0, sizeof(arr));
                x = 0;
                //添加矩阵到数组中
                for (k = i - 1; k <= i + 1; ++k)
                {
                    for (l = j - 1; l <= j + 1; ++l)
                    {
                        arr[x] = sgrayArray[k][l];
                        ++x;
                    }
                }
                //排序
                sort(arr, arr + 9);
                //取中位数
                dgrayArray[i][j] = arr[4];
            }
        }
        //绘制新的图像
        for (i = 0; i < height; ++i)
        {
            for (j = 0; j < width; ++j)
            {
                addArray(i, j);
                for (k = 0; k < 4; ++k)
                {
                    rgbarray[k] = dgrayArray[i][j];
                }
                popArray(i, j);
            }
        }
        char str[] = "medianfilter.bmp";
        printf("Succeed to median-filter on %s\n", str);
        saveBmp(str);
    }
    //加权均值滤波器，3x3矩阵
    void weightedmeanFilter()
    {
        //定义两个灰度数组并初始化，一个记录原始灰度值，另一个计算得出目的文件的灰度
        BYTE sgrayArray[height][width];
        BYTE dgrayArray[height][width];
        memset(sgrayArray, 0, sizeof(sgrayArray));
        memset(dgrayArray, 0, sizeof(dgrayArray));
        //图像转灰度
        convertRGB2Gray();
        //只储存3x3矩阵数值的数组
        BYTE arr[9];
        //权重数组
        BYTE w[9] = {1, 2, 1, 2, 4, 2, 1, 2, 1};
        long i, j;
        for (i = 0; i < height; ++i)
        {
            for (j = 0; j < width; ++j)
            {
                sgrayArray[i][j] = bgra[i][j].rgbBlue;
            }
        }
        memcpy_s(dgrayArray, sizeof(dgrayArray), sgrayArray, sizeof(sgrayArray));
        long k, l;
        int x;
        int wsum = 0;
        for (i = 1; i < height - 1; ++i)
        {
            for (j = 1; j < width - 1; ++j)
            {
                memset(arr, 0, sizeof(arr));
                x = 0;
                wsum = 0;
                //计算加权后的总和
                for (k = i - 1; k <= i + 1; ++k)
                {
                    for (l = j - 1; l <= j + 1; ++l)
                    {
                        arr[x] = sgrayArray[k][l];
                        wsum = wsum + arr[x];
                        ++x;
                    }
                }
                //算出目的文件的灰度值
                dgrayArray[i][j] = wsum / 16;
            }
        }
        //绘制新的图像
        for (i = 0; i < height; ++i)
        {
            for (j = 0; j < width; ++j)
            {
                addArray(i, j);
                for (k = 0; k < 4; ++k)
                {
                    rgbarray[k] = dgrayArray[i][j];
                }
                popArray(i, j);
            }
        }
        char str[] = "weightedmeanfilter.bmp";
        printf("Succeed to weightedmean-filter on %s\n", str);
        saveBmp(str);
    }
    double calContrast()
    { //转灰度
        convertRGB2Gray();
        BYTE arr[height][width];
        memset(arr, 0, sizeof(arr));
        long i, j;
        for (i = 0; i < height; ++i)
        {
            for (j = 0; j < width; ++j)
            {
                arr[i][j] = bgra[i][j].rgbBlue;
            }
        }
        double sum;
        int count = 0;
        for (i = 1; i < height - 1; ++i)
        {
            for (j = 1; j < width - 1; ++j)
            {
                count += 4;
                sum += pow(double(arr[i][j] - arr[i + 1][j]), 2.0) + pow(double(arr[i][j] - arr[i][j + 1]), 2.0) + pow(double(arr[i][j] - arr[i - 1][j]), 2.0) + pow(double(arr[i][j] - arr[i][j - 1]), 2.0);
            }
        }
        double c = sum / count;
        return c;
    }
};